<?php
/**
 * 时间位多2位，起始时间从19700101开始
 * positive + 1
 * time 43 69*4年
 * machine 8 256台机器
 * sequence 12 4096, 使用redis管理sequence，1ms内可能超过这个数字，其实可以不必加判断等待逻辑
 */

namespace joysec\snowflake;

class IdWorker
{
    //左移位数
    const TIME_SHIFT_BITS = 20;
    const WORKER_SHIFT_BITS = 12;
    //max
    const MAX_SEQUENCE = 4096;
    const MAX_WORKER = 256;
    //Redis加锁过期时间
    const REDIS_TIME_OUT = 100;

    /**
     * @var string redis host
     */
    private static $redisHost = "127.0.0.1";
    /**
     * @var int redis port
     */
    private static $redisPort = 6379;
    /**
     * @var int 上次时间戳
     */
    private static $lastTimestamp = 0;
    /**
     * @var int 机器编号
     */
    private static $machine = 0;

    /**
     * 生成id
     * @return int 通过算法生成的id
     */
    public static function nextId()
    {
        while(true) {
            $timestamp = self::timeGen();
            $sequence = self::getSequence($timestamp);
            if($sequence < 0) {
                sleep(0.01);
            } else {
                break;
            }
        }

        $nextId = $timestamp << self::TIME_SHIFT_BITS;
        $nextId |= self::$machine << self::WORKER_SHIFT_BITS | $sequence;
        self::$lastTimestamp = $timestamp;
        return $nextId;
    }

    /**
     * 设置机器编码
     * @param $id int 机器编码
     */
    public static function setMachine($id)
    {
        self::$machine = $id;
    }

    /**
     * 配置redis信息
     * @param $host string redis host
     * @param $port string redis端口
     */
    public static function setRedis($host, $port) {
        self::$redisHost = $host;
        self::$redisPort = $port;
    }

    /**
     * @return float 当前时间戳
     */
    private static function timeGen()
    {
        return floor(microtime(true) * 1000);
    }

    /**
     * @param $lastTimestamp int 上一个时间戳
     * @return float sequence可以置零的下一个时间戳
     */
    private static function tilNextMillis($lastTimestamp)
    {
        $timestamp = self::timeGen();
        while ($timestamp <= $lastTimestamp) {
            $timestamp = self::timeGen();
        }
        return $timestamp;
    }

    /**
     * 获取可用的sequence，可能会改变时间戳
     * @param $timestamp int 当前时间戳引用
     * @return int -1表示未能获取redis锁
     */
    private static function getSequence(&$timestamp) {
        $redis = new \Redis();
        $redis->connect(self::$redisHost, self::$redisPort);
        $result = $redis->setnx("lock:snowflake", $timestamp);
        $sequence = 0;
        //result == 0表示未redis加锁已经被别的进程占用，查看锁的时间戳，如果超时，表示别的进程出现问题，直接占用该锁
        if($result == 0) {
            $lock = $redis->get("lock:snowflake");
            if($timestamp > $lock + self::REDIS_TIME_OUT) {
                $redis->getSet("lock:snowflake", $timestamp);
                $result = 1;
            } else {
                $sequence = -1;
            }
        }
        if ($result == 1) {
            $lastTimestamp = $redis->get("snowflake:last_timestamp");
            if ($lastTimestamp == null) {
                $redis->set("snowflake:last_timestamp", $timestamp);
                $redis->set("snowflake:sequence", 0);
            } else if ($timestamp == $lastTimestamp) {
                $sequence = $redis->get("snowflake:sequence");
                if($sequence >= self::MAX_SEQUENCE) {
                    $timestamp = self::tilNextMillis($lastTimestamp);
                    $redis->set("snowflake:last_timestamp", $timestamp);
                    $sequence = 0;
                } else {
                    $sequence++;
                }
            } else {
                $sequence = 0;
                $redis->set("snowflake:last_timestamp", $timestamp);
            }
        }
        if($sequence >= 0) {
            $redis->set("snowflake:sequence", $sequence);
            $redis->del("lock:snowflake");
        }
        $redis->close();
        return $sequence;
    }
}
